/*******************************************************************************
 * Copyright (c) 2013 Matthew Gruda.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl.html
 * 
 * Contributors:
 *     matthew - initial API and implementation
 ******************************************************************************/
package terminal.gld;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.HashMap;
import java.util.Map;
import java.awt.GraphicsEnvironment;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.GLU;

/**
 * A TrueType font implementation originally for Slick, edited for Bobjob's
 * Engine
 * 
 * @original author James Chambers (Jimmy)
 * @original author Jeremy Adams (elias4444)
 * @original author Kevin Glass (kevglass)
 * @original author Peter Korzuszek (genail)
 * 
 * @new version edited by David Aaron Muhar (bobjob)
 */
public class TrueTypeFont {

	public final static int ALIGN_LEFT = 0, ALIGN_RIGHT = 1,
			ALIGN_CENTER = 2;

	/** Array that holds necessary information about the font characters */
	private IntObject[] charArray = new IntObject[256];

	/** Map of user defined font characters (Character <-> IntObject) */
	private Map<Character, IntObject> customChars = new HashMap<Character, IntObject>();

	/** Boolean flag on whether AntiAliasing is enabled or not */
	private boolean antiAlias;

	/** Font's size */
	private int fontSize = 0;

	/** Font's height */
	private int fontHeight = 0;

	/** Texture used to cache the font 0-255 characters */
	private int fontTextureID;

	/** Default font texture width */
	private int textureWidth = 512;

	/** Default font texture height */
	private int textureHeight = 512;

	/** A reference to Java's AWT Font that we create our font texture from */
	private Font font;

	/** The font metrics for our Java AWT font */
	private FontMetrics fontMetrics;

	private int correctL = 9, correctR = 8;

	private class IntObject {

		/** Character's width */
		public int width;

		/** Character's height */
		public int height;

		/** Character's stored x position */
		public int storedX;

		/** Character's stored y position */
		public int storedY;
	}

	public TrueTypeFont(Font font, boolean antiAlias, char[] additionalChars) {
		this.font = font;
		this.fontSize = font.getSize() + 3;
		this.antiAlias = antiAlias;

		createSet(additionalChars);

		fontHeight -= 1;
		if (fontHeight <= 0)
			fontHeight = 1;
	}

	public TrueTypeFont(Font font, boolean antiAlias) {
		this(font, antiAlias, null);
	}

	public void setCorrection(boolean on) {
		if (on) {
			correctL = 2;
			correctR = 1;
		} else {
			correctL = 0;
			correctR = 0;
		}
	}

	private BufferedImage getFontImage(char ch) {
		// Create a temporary image to extract the character's size
		BufferedImage tempfontImage = new BufferedImage(1, 1,
				BufferedImage.TYPE_INT_ARGB);
		Graphics2D g = (Graphics2D) tempfontImage.getGraphics();
		if (antiAlias == true) {
			g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
					RenderingHints.VALUE_ANTIALIAS_ON);
		}
		g.setFont(font);
		fontMetrics = g.getFontMetrics();
		int charwidth = fontMetrics.charWidth(ch) + 8;

		if (charwidth <= 0) {
			charwidth = 7;
		}
		int charheight = fontMetrics.getHeight() + 3;
		if (charheight <= 0) {
			charheight = fontSize;
		}

		// Create another image holding the character we are creating
		BufferedImage fontImage;
		fontImage = new BufferedImage(charwidth, charheight,
				BufferedImage.TYPE_INT_ARGB);
		Graphics2D gt = (Graphics2D) fontImage.getGraphics();
		if (antiAlias == true) {
			gt.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
					RenderingHints.VALUE_ANTIALIAS_ON);
		}
		gt.setFont(font);

		gt.setColor(Color.WHITE);
		int charx = 3;
		int chary = 1;
		gt.drawString(String.valueOf(ch), (charx), (chary)
				+ fontMetrics.getAscent());

		return fontImage;

	}

	private void createSet(char[] customCharsArray) {
		// If there are custom chars then I expand the font texture
		// twice
		if (customCharsArray != null && customCharsArray.length > 0) {
			textureWidth *= 2;
		}

		// In any case this should be done in other way. Texture with
		// size 512x512
		// can maintain only 256 characters with resolution of 32x32.
		// The texture
		// size should be calculated dynamicaly by looking at character
		// sizes.

		try {

			BufferedImage imgTemp = new BufferedImage(textureWidth,
					textureHeight,
					BufferedImage.TYPE_INT_ARGB);
			Graphics2D g = (Graphics2D) imgTemp.getGraphics();

			g.setColor(new Color(0, 0, 0, 1));
			g.fillRect(0, 0, textureWidth, textureHeight);

			int rowHeight = 0;
			int positionX = 0;
			int positionY = 0;

			int customCharsLength = (customCharsArray != null) ? customCharsArray.length
					: 0;

			for (int i = 0; i < 256 + customCharsLength; i++) {

				// get 0-255 characters and then custom
				// characters
				char ch = (i < 256) ? (char) i
						: customCharsArray[i - 256];

				BufferedImage fontImage = getFontImage(ch);

				IntObject newIntObject = new IntObject();

				newIntObject.width = fontImage.getWidth();
				newIntObject.height = fontImage.getHeight();

				if (positionX + newIntObject.width >= textureWidth) {
					positionX = 0;
					positionY += rowHeight;
					rowHeight = 0;
				}

				newIntObject.storedX = positionX;
				newIntObject.storedY = positionY;

				if (newIntObject.height > fontHeight) {
					fontHeight = newIntObject.height;
				}

				if (newIntObject.height > rowHeight) {
					rowHeight = newIntObject.height;
				}

				// Draw it here
				g.drawImage(fontImage, positionX, positionY,
						null);

				positionX += newIntObject.width;

				if (i < 256) { // standard characters
					charArray[i] = newIntObject;
				} else { // custom characters
					customChars.put(new Character(ch),
							newIntObject);
				}

				fontImage = null;
			}

			fontTextureID = loadImage(imgTemp);

			// .getTexture(font.toString(), imgTemp);

		} catch (Exception e) {
			System.err.println("Failed to create font.");
			e.printStackTrace();
		}
	}

	private void drawQuad(float drawX, float drawY, float drawX2,
			float drawY2, float srcX, float srcY, float srcX2,
			float srcY2) {
		float DrawWidth = drawX2 - drawX;
		float DrawHeight = drawY2 - drawY;
		float TextureSrcX = -.01f + srcX / textureWidth;
		float TextureSrcY = srcY / textureHeight;
		float SrcWidth = srcX2 - srcX;
		float SrcHeight = srcY2 - srcY;
		float RenderWidth = (SrcWidth / textureWidth);
		float RenderHeight = (SrcHeight / textureHeight);

		GL11.glTexCoord2f(TextureSrcX, TextureSrcY);
		GL11.glVertex3f(drawX, drawY, .1f);
		GL11.glTexCoord2f(TextureSrcX, TextureSrcY + RenderHeight);
		GL11.glVertex3f(drawX, drawY + DrawHeight, .1f);
		GL11.glTexCoord2f(TextureSrcX + RenderWidth, TextureSrcY
				+ RenderHeight);
		GL11.glVertex3f(drawX + DrawWidth, drawY + DrawHeight, .1f);
		GL11.glTexCoord2f(TextureSrcX + RenderWidth, TextureSrcY);
		GL11.glVertex3f(drawX + DrawWidth, drawY, .1f);
	}

	public int getWidth(String whatchars) {
		int totalwidth = 0;
		IntObject intObject = null;
		int currentChar = 0;
		for (int i = 0; i < whatchars.length(); i++) {
			currentChar = whatchars.charAt(i);
			if (currentChar < 256) {
				intObject = charArray[currentChar];
			} else {
				intObject = customChars.get(new Character(
						(char) currentChar));
			}

			if (intObject != null)
				totalwidth += intObject.width;
		}
		return totalwidth;
	}

	public int getHeight() {
		return fontHeight;
	}

	public int getHeight(String HeightString) {
		return fontHeight;
	}

	public int getLineHeight() {
		return fontHeight;
	}

	public void drawString(float x, float y, String whatchars,
			float scaleX, float scaleY) {
		drawString(x, y, whatchars, 0, whatchars.length() - 1, scaleX,
				scaleY, ALIGN_LEFT, null);
	}

	public void drawString(float x, float y, String whatchars,
			float scaleX, float scaleY, int format) {
		drawString(x, y, whatchars, 0, whatchars.length() - 1, scaleX,
				scaleY, format, null);
	}

	public void drawString(float x, float y, String whatchars,
			int startIndex, int endIndex, float scaleX,
			float scaleY, int format, Color[] colarr) {

		IntObject intObject = null;
		int charCurrent;

		int totalwidth = 0;
		int i = startIndex, d, c;
		float startY = 0;

		switch (format) {
			case ALIGN_RIGHT: {
				d = -1;
				c = correctR;

				while (i < endIndex) {
					if (whatchars.charAt(i) == '\n')
						startY -= fontHeight;
					i++;
				}
				break;
			}
			case ALIGN_CENTER: {
				for (int l = startIndex; l <= endIndex; l++) {
					charCurrent = whatchars.charAt(l);
					if (charCurrent == '\n')
						break;
					if (charCurrent < 256) {
						intObject = charArray[charCurrent];
					} else {
						intObject = customChars
								.get(new Character(
										(char) charCurrent));
					}
					totalwidth += intObject.width
							- correctL;
				}
				totalwidth /= -2;
			}
			case ALIGN_LEFT:
			default: {
				d = 1;
				c = correctL;
				break;
			}

		}

		GL11.glBindTexture(GL11.GL_TEXTURE_2D, fontTextureID);
		GL11.glBegin(GL11.GL_QUADS);

		boolean usecol = colarr != null
				&& colarr.length == whatchars.length();

		while (i >= startIndex && i <= endIndex) {

			charCurrent = whatchars.charAt(i);
			if (charCurrent < 256) {
				intObject = charArray[charCurrent];
			} else {
				intObject = customChars.get(new Character(
						(char) charCurrent));
			}

			if (intObject != null) {
				if (d < 0)
					totalwidth += (intObject.width - c) * d;
				if (charCurrent == '\n') {
					startY -= fontHeight * d;
					totalwidth = 0;
					if (format == ALIGN_CENTER) {
						for (int l = i + 1; l <= endIndex; l++) {
							charCurrent = whatchars
									.charAt(l);
							if (charCurrent == '\n')
								break;
							if (charCurrent < 256) {
								intObject = charArray[charCurrent];
							} else {
								intObject = customChars
										.get(new Character(
												(char) charCurrent));
							}
							totalwidth += intObject.width
									- correctL;
						}
						totalwidth /= -2;
					}
					// if center get next lines total
					// width/2;
				} else {
					if (usecol) {
						Color col = colarr[i];
						GL11.glColor4f( col
								.getRed() / 255f,
								 col.getGreen() / 255f,
								 col.getBlue() / 255f,
								col.getAlpha() / 255f);
						
					}
					drawQuad((totalwidth + intObject.width)
							* scaleX + x,
							startY * scaleY + y,
							totalwidth * scaleX + x,
							(startY + intObject.height)
									* scaleY
									+ y,
							intObject.storedX
									+ intObject.width,
							intObject.storedY
									+ intObject.height,
							intObject.storedX,
							intObject.storedY);
					if (d > 0)
						totalwidth += (intObject.width - c)
								* d;
				}
				i += d;

			}
		}
		GL11.glEnd();
	}

	public static int loadImage(BufferedImage bufferedImage) {
		try {
			short width = (short) bufferedImage.getWidth();
			short height = (short) bufferedImage.getHeight();
			// textureLoader.bpp =
			// bufferedImage.getColorModel().hasAlpha() ? (byte)32 :
			// (byte)24;
			int bpp = (byte) bufferedImage.getColorModel()
					.getPixelSize();
			ByteBuffer byteBuffer;
			DataBuffer db = bufferedImage.getData().getDataBuffer();
			if (db instanceof DataBufferInt) {
				int intI[] = ((DataBufferInt) (bufferedImage
						.getData().getDataBuffer()))
						.getData();
				byte newI[] = new byte[intI.length * 4];
				for (int i = 0; i < intI.length; i++) {
					byte b[] = intToByteArray(intI[i]);
					int newIndex = i * 4;

					newI[newIndex] = b[1];
					newI[newIndex + 1] = b[2];
					newI[newIndex + 2] = b[3];
					newI[newIndex + 3] = b[0];
				}

				byteBuffer = ByteBuffer
						.allocateDirect(width * height
								* (bpp / 8))
						.order(ByteOrder.nativeOrder())
						.put(newI);
			} else {
				byteBuffer = ByteBuffer
						.allocateDirect(width * height
								* (bpp / 8))
						.order(ByteOrder.nativeOrder())
						.put(((DataBufferByte) (bufferedImage
								.getData()
								.getDataBuffer()))
								.getData());
			}
			byteBuffer.flip();

			int internalFormat = GL11.GL_RGBA8, format = GL11.GL_RGBA;
			IntBuffer textureId = BufferUtils.createIntBuffer(1);
			;
			GL11.glGenTextures(textureId);
			GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureId.get(0));

			GL11.glTexParameteri(GL11.GL_TEXTURE_2D,
					GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP);
			GL11.glTexParameteri(GL11.GL_TEXTURE_2D,
					GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP);

			GL11.glTexParameteri(GL11.GL_TEXTURE_2D,
					GL11.GL_TEXTURE_MAG_FILTER,
					GL11.GL_LINEAR);
			GL11.glTexParameteri(GL11.GL_TEXTURE_2D,
					GL11.GL_TEXTURE_MIN_FILTER,
					GL11.GL_LINEAR);

			GL11.glTexEnvf(GL11.GL_TEXTURE_ENV,
					GL11.GL_TEXTURE_ENV_MODE,
					GL11.GL_MODULATE);

			GLU.gluBuild2DMipmaps(GL11.GL_TEXTURE_2D,
					internalFormat, width, height, format,
					GL11.GL_UNSIGNED_BYTE, byteBuffer);
			return textureId.get(0);

		} catch (Exception e) {
			e.printStackTrace();
			System.exit(-1);
		}

		return -1;
	}

	public static boolean isSupported(String fontname) {
		Font font[] = getFonts();
		for (int i = font.length - 1; i >= 0; i--) {
			if (font[i].getName().equalsIgnoreCase(fontname))
				return true;
		}
		return false;
	}

	public static Font[] getFonts() {
		return GraphicsEnvironment.getLocalGraphicsEnvironment()
				.getAllFonts();
	}

	public static byte[] intToByteArray(int value) {
		return new byte[] {
				(byte) (value >>> 24), (byte) (value >>> 16),
				(byte) (value >>> 8), (byte) value
		};
	}

	public void destroy() {
		IntBuffer scratch = BufferUtils.createIntBuffer(1);
		scratch.put(0, fontTextureID);
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
		GL11.glDeleteTextures(scratch);
	}
}
